package cn.k7g.alloy.autoconfiguration;


import cn.k7g.alloy.annotation.MoldContent;
import cn.k7g.alloy.annotation.MoldParam;
import cn.k7g.alloy.contants.AlloyConstant;
import cn.k7g.alloy.ioc.processor.EnhanceExceptionResponse;
import cn.k7g.alloy.mold.BaseMold;
import cn.k7g.alloy.mold.MoldService;
import cn.k7g.alloy.mold.var.AbsVar;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Predicate;

/**
 * bean definition注册类
 * @author victor-wu
 * @date 2024年4月3日
 */
@Slf4j
public class AlloyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        AnnotationAttributes enableAlloy = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableAlloyAutoConfiguration.class.getName(), false));

        try {
            String[] moldBasePackages = AlloyAutoConfigurationSelector.enableAlloy.getStringArray("moldBasePackages");
            if (moldBasePackages != null && moldBasePackages.length > 0) {
                scanMoldContent(registry, importBeanNameGenerator, moldBasePackages);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (BeanDefinitionStoreException e) {
            throw new RuntimeException(e);
        }

        Properties properties = new Properties();
        String[] enhanceExceptionMessageProperties = enableAlloy.getStringArray("enhanceExceptionMessageProperties");
        for (String enhanceExceptionMessageProperty : enhanceExceptionMessageProperties) {
            try {
                properties.load(ResourceUtils.getURL(enhanceExceptionMessageProperty).openStream());
            } catch (Exception e) {
                log.debug("加载文件错误", e);
            }
        }
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Properties.class, () -> properties);
        registry.registerBeanDefinition(EnhanceExceptionResponse.EXCEPTION_MESSAGES_PROPERTIES_BEAN_NAME, beanDefinitionBuilder.getRawBeanDefinition());
    }

    private void scanMoldContent(BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator, String[] moldBasePackages) throws ClassNotFoundException {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return true;
            }
        };
        scanner.resetFilters(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(MoldContent.class));
        for (String basePackage : moldBasePackages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition beanDefinition : candidateComponents) {
                // 创建一个 FactoryBean 的 BeanDefinition
                BeanDefinitionBuilder factoryBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MoldContentFactoryBean.class);
                factoryBeanDefinitionBuilder.addPropertyValue("beanCls", Class.forName(beanDefinition.getBeanClassName()));
                factoryBeanDefinitionBuilder.addPropertyReference("moldService", AlloyConstant.BEAN_NAME_MOLD_SERVICE);
                BeanDefinition factoryBeanDefinition = factoryBeanDefinitionBuilder.getBeanDefinition();
                registry.registerBeanDefinition(importBeanNameGenerator.generateBeanName(beanDefinition, registry), factoryBeanDefinition);
            }
        }
    }


    public static class MoldContentFactoryBean<T> implements FactoryBean<T> {

        public void setBeanCls(Class<T> beanCls) {
            this.beanCls = beanCls;
        }

        public void setMoldService(MoldService moldService) {
            this.moldService = moldService;
        }

        private Class<T> beanCls;
        private MoldService moldService;

        @SuppressWarnings("unchecked")
        @Override
        public T getObject() throws Exception {
            return (T) Proxy.newProxyInstance(MoldContentFactoryBean.class.getClassLoader(), new Class[]{beanCls}, (Object target, Method method, Object[] argv) -> {

                MoldContent objectMoldContent = beanCls.getAnnotation(MoldContent.class);
                MoldContent methodMoldContent = method.getAnnotation(MoldContent.class);
                Predicate<MoldContent.Level> NotEqualsDefault = o -> o != MoldContent.Level.DEFAULT;

                MoldContent.Level execLevel = Optional.of(methodMoldContent.execLevel())
                        .filter(NotEqualsDefault)
                        .orElseGet(() -> Optional.of(objectMoldContent.execLevel()).filter(NotEqualsDefault).orElse(MoldContent.Level.SAFE))
                        ;

                String value = Optional.ofNullable(methodMoldContent.value()).orElse(objectMoldContent.value());;
                String trigger = Optional.ofNullable(methodMoldContent.trigger()).orElse(objectMoldContent.trigger());

//                if (methodMoldContent == null) {
//                    throw new NullPointerException("@MoldContent 未设置");
//                }
                BaseMold baseMold = new BaseMold().setContent(value);
                for (int i = 0; i < method.getParameterAnnotations().length; i++) {
                    Parameter parameter = method.getParameters()[i];
                    MoldParam param = findAnno(method.getParameterAnnotations()[i], MoldParam.class);
                    if (param == null) {
                        baseMold.addConstVar(parameter.getName(), (Serializable) argv[i]);
                    } else {
                        Class<? extends AbsVar> var = param.var();
                        Constructor<? extends AbsVar> constructor = var.getConstructor(String.class, parameter.getType());
                        baseMold.addVar(constructor.newInstance(param.name(), argv[i]));
                    }
                }

                String msg;
                switch (execLevel) {
                    case SAFE:
                        msg = moldService.createContent(baseMold).toString();
                        break;
                    case MIX_UNSAFE:
                        msg = moldService.createContentMixUnsafe(baseMold).toString();
                        break;
                    case UNCONTROLLED:
                        msg = moldService.createContentUncontrolled(baseMold).toString();
                        break;
                    default:
                        throw new RuntimeException("未知的执行方式");
                }

                Object triggerRet = null;
                if (StringUtils.hasText(trigger)) {
                    BaseMold triggerMold = new BaseMold()
                            .setContent(trigger)
                            .addConstVar("_result", msg)
                            ;
                    triggerRet = moldService.createContentUncontrolled(triggerMold);
                }

                if (method.getReturnType() == void.class) {
                    return null;
                }

                if (triggerRet != null && triggerRet.getClass() == method.getReturnType()) {
                    return triggerRet;
                }

                if (method.getReturnType() == String.class) {
                    return msg;
                }
                return null;
            });
        }

        public <T> T findAnno(Annotation[] arr, Class<T> find) {
            for (Annotation annotation : arr) {
                if (annotation.annotationType() == find) {
                    return (T) annotation;
                }
            }
            return null;
        }

        @Override
        public Class<?> getObjectType() {
            return this.beanCls;
        }

    }
}
